//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2006-2007
//
// Select type dialog.
//

# include "StdAfx.h"
# include "Resource.h"
# include "SelectType.h"
# include "Types.h"
# include "TypeLocalization.h"
# include "Map.h"
# include "ParseHelpers.h"
# include "Dialog.h"
# include "ListView.h"
# include "Settings.h"
# include "Globals.h"

bool IsTypeSelected (const selected_types_t & _sts, RGN_KIND _Kind, USHORT _ushType) {
	const size_t cTypes = _sts.size ();
	for (size_t c = 0; c < cTypes; ++ c) {
		const selected_type_t & st = _sts [c];
		if (st.Kind == _Kind && st.ushType == _ushType)
			return true;
	}
	return false;
}

/////////////////////////////////////

/*
static WNDPROC g_wpTypeListView = NULL;

static
LRESULT APIENTRY _TypeListViewProc (HWND _hWnd, UINT _uMsg, WPARAM _wParam, LPARAM _lParam) {
	switch (_uMsg) {
		case LVM_FINDITEM: {
			const LVFINDINFO * const pFindInfo = reinterpret_cast <const LVFINDINFO *> (_lParam);
			if ((pFindInfo->flags & (LVFI_PARTIAL | LVFI_STRING)) == 0)
				break;

			const bool bWrap = (pFindInfo->flags & LVFI_WRAP) != 0;

			const size_t cPrefixLen = ::_tcslen (pFindInfo->psz);

			size_t cItemFoundBefore = -1;
			size_t cItemFoundAfter  = -1;
			bool bFoundBefore = false;
			bool bFoundAfter  = false;
			{
				const HWND hList = _hWnd;
				const size_t cItems = ListView_GetItemCount (hList);
				for (size_t cItem = 0; cItem < cItems; ++ cItem) {
					const bool bBeforeStart = cItem < (size_t) _wParam;

					TCHAR strName [64];
					ListView_GetItemText (hList, cItem, lvtDescription, strName, sizeof (strName)/sizeof (strName [0]));

					const size_t cNameLen = ::_tcslen (strName);

					if (
						cNameLen >= cPrefixLen &&
						::CompareString (
							LOCALE_USER_DEFAULT, NORM_IGNORECASE,
							strName,        cPrefixLen,
							pFindInfo->psz, cPrefixLen
						) == CSTR_EQUAL
					) {
						if (bBeforeStart) {
							if (bFoundBefore == false)
								cItemFoundBefore = cItem;
							bFoundBefore = true;
						} else {
							if (bFoundAfter == false)
								cItemFoundAfter = cItem;
							bFoundAfter = true;
							break;
						}
					}

					++ cItem;
				}
			}

			if (bFoundAfter)
				return cItemFoundAfter;
			else
				return (bWrap && bFoundBefore) ? cItemFoundBefore : -1;
		}
	}

	assert (g_wpTypeListView);
	return ::CallWindowProc (g_wpTypeListView, _hWnd, _uMsg, _wParam, _lParam);
}*/

enum {
	lvtCode        = 0,
	lvtKind        = 1,
	lvtCategory    = 2,
	lvtDescription = 3,
	lvtLocDescr    = 4
};

const char c_strShowUsedTypes [] = "ShowUsedTypes";

class CSelectType : public CDialog {
  public:
	CSelectType (const CTypeSet * _pTypeSet, RGN_KIND _Kind, USHORT & _ushType, pfApplySelectType_t _pfApplySelectType) :
		m_pTypeSet (_pTypeSet),
		m_Kind (_Kind),
		m_pushType (& _ushType),
		m_pfApplySelectType (_pfApplySelectType),
		m_pSelectedTypes (NULL),
		CDialog (IDD_SELECT_TYPE)
	{}

	CSelectType (const CTypeSet * _pTypeSet, selected_types_t & _types) :
		m_pTypeSet (_pTypeSet),
		m_Kind (rkMax),
		m_pushType (NULL),
		m_pfApplySelectType (NULL),
		m_pSelectedTypes (& _types),
		CDialog (IDD_SELECT_TYPES)
	{}

	// Override.
	virtual bool OnInit (HWND _hwndFocus);
	virtual void OnDestroy ();
	virtual void OnCommand (WORD _wCmd, WORD _wNotifyCode, HWND _hwndCtl);
	virtual bool OnNotify (int _idCtrl, NMHDR * _pInfo);

  private:
	const CTypeSet * const m_pTypeSet;
	const RGN_KIND m_Kind;
	USHORT * const m_pushType;
	const pfApplySelectType_t m_pfApplySelectType;
	selected_types_t * const m_pSelectedTypes;

	CListView m_Types;

	bool _ParseCode (USHORT & _ushCode);
	void _FillCode  (USHORT _ushCode);
	void _FillTypes (bool _bShowUsedTypes);
	void _FillType  (size_t _cItem, const type_desc_t & _td, DWORD _dwTypeAndKind, RGN_KIND _Kind, USHORT _ushType);
	void _EnableOK  (bool _bEnable);
	void _GetType (size_t _cItem, RGN_KIND & _Kind, USHORT & _ushType);
	USHORT _GetType (size_t _cItem);
	const type_desc_t * _GetTypeDesc (size_t _cItem);
};

bool CSelectType::_ParseCode (USHORT & _ushCode) {
	char strCode [20];
	GetDlgItemText (IDC_CODE, strCode, sizeof (strCode));

	unsigned long ulCode = 0;
	if (strCode [0] == '0' && (strCode [1] == 'x' || strCode [1] == 'X')) {
		const char * pEnd = NULL;
		if (! ParseHex (strCode + 2, strCode + sizeof (strCode), & pEnd, ulCode))
			return false;
	} else {
		const char * pEnd = NULL;
		if (! ParseDecU (strCode, strCode + sizeof (strCode), & pEnd, ulCode))
			return false;
	}

	_ushCode = ulCode;
	return true;
}

void CSelectType::_FillCode (USHORT _ushCode) {
	USHORT ushPrevCode = 0;
	if (! _ParseCode (ushPrevCode) || ushPrevCode != _ushCode) {
		char strCode [20];
		::sprintf (strCode, "0x%02x", _ushCode);
		SetDlgItemText (IDC_CODE, strCode);
	}
}

void CSelectType::_FillType (size_t _cItem, const type_desc_t & _td, DWORD _dwTypeAndKind, RGN_KIND _Kind, USHORT _ushType) {
	// Code & icon.
	InsertItem  (m_Types, _cItem, _dwTypeAndKind, "0x%04x", _ushType);
	SetItemIcon (m_Types, _cItem, _td.cImageListIndex);

	// Kind.
	SetSubItemText (m_Types, _cItem, lvtKind,        "%s", g_strKinds [_Kind]);

	// Category.
	SetSubItemText (m_Types, _cItem, lvtCategory,    "%s", GetTypeCategoryDisplayName (_td.Category));

	// Description.
	SetSubItemText (m_Types, _cItem, lvtDescription, "%s", _td.strTypeName);

	// Localized description.
	if (m_pTypeSet->bHasLocalizedTypes && _td.strTypeNameLocalized)
		SetSubItemText (m_Types, _cItem, lvtLocDescr, "%s", _td.strTypeNameLocalized);

	if (m_pushType && _ushType == * m_pushType && _Kind == m_Kind)
		SetSelection (m_Types, _cItem);

	if (m_pSelectedTypes && IsTypeSelected (* m_pSelectedTypes, _Kind, _ushType))
		ListView_SetCheckState (m_Types, _cItem, TRUE);
}

void CSelectType::_FillTypes (bool _bShowUsedTypes) {
	ListView_DeleteAllItems (m_Types);

	typedef std::set<DWORD> setTypes_t;
	// Types not defined in type set.
	setTypes_t setUnknownTypes;
	// Subtypes of ranges defined in type set.
	setTypes_t setSpecificTypes;

	const size_t cTypes = m_pTypeSet->cTypes;
	bool * const pbShowType = reinterpret_cast<bool *> (alloca (sizeof (bool)*cTypes));
	if (_bShowUsedTypes || m_pushType == NULL) {
		::memset (pbShowType, 0, sizeof (bool)*cTypes);

		// Count types.
		for (size_t cKind = 0; cKind < rkMax; ++ cKind) {
			const rgns_t & rgns = g_map.layers [cKind].rgns;
			for (rgns_t::const_iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
				const rgn_t & rgn = * iRgn;
				if (rgn.IsDeleted () || rgn.IsWTR ())
					continue;

				// Count types.
				const type_desc_t * const pTD = rgn.GetTypeDesc ();
				assert (pTD);
				if (pTD && ! pTD->bUnknown) {
					if (pTD->ushType == rgn.ushType)
						pbShowType [pTD->cOrdinal] = true;
					else {
						const DWORD dwType = rgn.ushType + (DWORD (rgn.Kind) << 16);
						setSpecificTypes.insert (dwType);
					}
				} else {
					const DWORD dwType = rgn.ushType + (DWORD (rgn.Kind) << 16);
					setUnknownTypes.insert (dwType);
				}
			}
		}
	} else
		::memset (pbShowType, 1, sizeof (bool)*cTypes);

	// For each type.
	// NOTE: omit 0-th type ("unknown").
	size_t cItem = 0;
	for (size_t cType = 1; cType < cTypes; ++ cType) {
		const type_desc_t & td = m_pTypeSet->pTypes [cType];
		if (m_Kind != rkMax && td.Kind != m_Kind)
			continue;
		if (! pbShowType [td.cOrdinal])
			continue;

		_FillType (cItem, td, td.ulType, static_cast<RGN_KIND> (td.Kind), td.ushType);
		++ cItem;
	}

	const type_desc_t * const pUnknownTD = m_pTypeSet->GetUnknownTD ();

	// Specific subtypes of ranges.
	setTypes_t::iterator i;
	for (i = setSpecificTypes.begin (); i != setSpecificTypes.end (); ++ i) {
		const DWORD dwTypeAndKind = * i;
		const USHORT ushType = dwTypeAndKind & 0xFFFF;
		const RGN_KIND Kind  = static_cast<RGN_KIND> (dwTypeAndKind >> 16);
		assert (Kind < rkMax);

		if (m_Kind != rkMax && Kind != m_Kind)
			continue;

		const type_desc_t * const pTD = g_map.pTypeSet->GetTypeDesc (ushType, Kind);
		_FillType (cItem, pTD ? * pTD : * pUnknownTD, dwTypeAndKind, Kind, ushType);
		++ cItem;
	}

	// Unknown types.
	for (i = setUnknownTypes.begin (); i != setUnknownTypes.end (); ++ i) {
		const DWORD dwTypeAndKind = * i;
		const USHORT ushType = dwTypeAndKind & 0xFFFF;
		const RGN_KIND Kind  = static_cast<RGN_KIND> (dwTypeAndKind >> 16);
		assert (Kind < rkMax);

		if (m_Kind != rkMax && Kind != m_Kind)
			continue;

		_FillType (cItem, * pUnknownTD, dwTypeAndKind, Kind, ushType);
		++ cItem;
	}

	m_Types.Sort ();

	// Make sure that current type is visible.
	UpdateSelectionMark    (m_Types);
	EnsureSelectionVisible (m_Types);
}

bool CSelectType::OnInit (HWND _hwndFocus) {
	// Code.
	if (m_pushType)
		_FillCode (* m_pushType);

	// Show used/all types.
	const bool bShowUsedTypes = GetSetting (c_strShowUsedTypes, false);
	CheckDlgButton (IDC_SHOW_USED_ONLY, bShowUsedTypes ? BST_CHECKED : BST_UNCHECKED);
	
	//
	// Prepare list view control for types.
	//

	m_Types.Init (m_hWnd, IDC_TYPES, m_Kind == rkMax ? "SelectType" : "ChangeType", lvtCode);

	// Subclass the list view to hook LVM_FINDITEM.
//	g_wpTypeListView = reinterpret_cast<WNDPROC> (::GetWindowLong (hTypes, GWL_WNDPROC));
//	::SetWindowLong (hTypes, GWL_WNDPROC, reinterpret_cast<long> (_TypeListViewProc));

	m_Types.SetExtStyle (m_Kind != rkMax ? LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP : (LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_CHECKBOXES));
	m_Types.SetImageList (m_pTypeSet->GetTypeIcons (), true);

	AddColumn (m_Types, lvtCode,        "Code");
	AddColumn (m_Types, lvtKind,        "Kind");
	AddColumn (m_Types, lvtCategory,    "Category");
	AddColumn (m_Types, lvtDescription, "Description");
	if (m_pTypeSet->bHasLocalizedTypes)
		AddColumn (m_Types, lvtLocDescr, g_strTypeLocalizationName.c_str ());

	_FillTypes (bShowUsedTypes);

	// Resize columns.
	AutoSizeColumns (m_Types);
	if (m_Kind != rkMax)
		ListView_SetColumnWidth (m_Types, lvtKind, 0);
	m_Types.LoadHeaders ();

	return true;
}

void CSelectType::OnDestroy () {
	m_Types.SaveHeaders ();
	m_Types.Destroy ();
}

void CSelectType::_EnableOK (bool _bEnable) {
	if (m_Kind == rkMax)
		return;

	EnableDlgItem (IDOK,      _bEnable);
	EnableDlgItem (IDC_APPLY, _bEnable && m_pfApplySelectType != NULL);
}

void CSelectType::_GetType (size_t _cItem, RGN_KIND & _Kind, USHORT & _ushType) {
	const DWORD dwTypeAndKind = GetLParam (m_Types, _cItem);
	_ushType = dwTypeAndKind & dwTypeAndKind;
	_Kind  = static_cast<RGN_KIND> (dwTypeAndKind >> 16);
	assert (_Kind < rkMax);
}

USHORT CSelectType::_GetType (size_t _cItem) {
	RGN_KIND Kind;
	USHORT ushType = 0;
	_GetType (_cItem, Kind, ushType);
	return ushType;
}

const type_desc_t * CSelectType::_GetTypeDesc (size_t _cItem) {
	RGN_KIND Kind;
	USHORT ushType;
	_GetType (_cItem, Kind, ushType);

	return m_pTypeSet->GetTypeDesc (ushType, Kind);
}

void CSelectType::OnCommand (WORD _wCmd, WORD _wNotifyCode, HWND _hwndCtl) {
	switch (_wCmd) {
		case IDC_CODE: {
			if (_wNotifyCode == EN_CHANGE) {
				USHORT ushCode = 0;
				if (! _ParseCode (ushCode)) {
					// Disable 'Apply'.
					_EnableOK (false);
					return;
				}

				// Enable 'Apply'.
				_EnableOK (true);

				RemoveSelection (m_Types);

				// Search for the type in the list.
				const size_t cItems = ListView_GetItemCount (m_Types);
				for (size_t cItem = 0; cItem < cItems; ++ cItem) {
					const USHORT ushType = _GetType (cItem);
					if (ushType == ushCode) {
						SetSelection (m_Types, cItem);
						EnsureSelectionVisible (m_Types);
						break;
					}
				}
			}
			return;
		}

		case IDC_UNSELECT_ALL: {
			const size_t cItems = ListView_GetItemCount (m_Types);
			for (size_t cItem = 0; cItem < cItems; ++ cItem)
				ListView_SetCheckState (m_Types, cItem, FALSE);
			return;
		}

		case IDC_SHOW_USED_ONLY: {
			const bool bShowUsedTypes = IsDlgButtonChecked (IDC_SHOW_USED_ONLY);
			SaveSetting (c_strShowUsedTypes, bShowUsedTypes);
			_FillTypes (bShowUsedTypes);
			return;
		}

		case IDC_APPLY: {
			// Get new type.
			USHORT ushCode = 0;
			if (! _ParseCode (ushCode))
				break;

			if (m_pfApplySelectType)
				(* m_pfApplySelectType) (ushCode, m_Kind);
			return;
		}

		case IDOK: {
			if (m_pushType) {
				// Get new type.
				USHORT ushCode = 0;
				if (! _ParseCode (ushCode))
					break;
				* m_pushType = ushCode;
			}

			if (m_pSelectedTypes) {
				m_pSelectedTypes->clear ();
							
				const size_t cItems = ListView_GetItemCount (m_Types);
				for (size_t cItem = 0; cItem < cItems; ++ cItem) {
					if (ListView_GetCheckState (m_Types, cItem)) {
						m_pSelectedTypes->push_back (selected_type_t ());
						selected_type_t & st = m_pSelectedTypes->back ();
						_GetType (cItem, st.Kind, st.ushType);
					}
				}
			}

			EndDialog (0);
			return;
		}

		case IDCANCEL:
			EndDialog (1);
			return;
	}
}

bool CSelectType::OnNotify (int _idCtrl, NMHDR * _pInfo) {
	switch (_pInfo->code) {
		case NM_RCLICK:
			m_Types.PopupMenu_OnHeaderRClick (_pInfo);
			return false;

		case LVN_COLUMNCLICK: {
			// Save selection.
			const size_t cSel = ListView_GetSelectionMark (m_Types);
			const LPARAM lParamSel = cSel == -1 ? -1 : GetLParam (m_Types, cSel);

			m_Types.Sort_OnColumnClick (_pInfo);

			// Restore selection.
			if (lParamSel != -1)
				m_Types.SelectByLParam (lParamSel);

			break;
		}

		case LVN_ITEMCHANGED: {
			const NMLISTVIEW * const pInfo2 = reinterpret_cast<const NMLISTVIEW *> (_pInfo);
			if (
				(pInfo2->uOldState & LVIS_SELECTED) == 0 &&
				(pInfo2->uNewState & LVIS_SELECTED) != 0
			) {
				// Get new type.
				const size_t cSel = pInfo2->iItem;
				if (cSel == -1)
					break;
				const USHORT ushType = _GetType (cSel);

				// Update code.
				_FillCode (ushType);

				// Enable 'Apply'.
				_EnableOK (true);
			}
			break;
		}

		case NM_CUSTOMDRAW: {
			// Highlight categories using background color.
			if (m_Types.GetSortingColumn () != lvtCategory)
				break;

			NMLVCUSTOMDRAW * const pInfo2 = reinterpret_cast<NMLVCUSTOMDRAW *> (_pInfo);
			if (pInfo2->nmcd.dwDrawStage == CDDS_PREPAINT) {
				SetMsgResult (CDRF_NOTIFYITEMDRAW);
				return true;
			} else if (pInfo2->nmcd.dwDrawStage == CDDS_ITEMPREPAINT) {
				const type_desc_t * const pTD = _GetTypeDesc (pInfo2->nmcd.dwItemSpec);
				COLORREF col = pTD ? GetTypeCategoryColor (pTD->Category) : c_colWhite;
				if (col == c_colWhite)
					col = ::GetSysColor (COLOR_WINDOW);

				pInfo2->clrTextBk = col;
				SetMsgResult (CDRF_NEWFONT);
				return true;
			}
			break;
		}

		case NM_RETURN:
		case NM_DBLCLK:
			SendOK ();
			break;
	}

	return false;
}

///////////////////////////////////////////////////////////

bool SelectType (HWND _hWnd, const CTypeSet * _pTypeSet, RGN_KIND _Kind, USHORT & _ushType, pfApplySelectType_t _pfApplySelectType) {
	CSelectType st_dlg (_pTypeSet, _Kind, _ushType, _pfApplySelectType);
	return st_dlg.ShowModal (_hWnd) == 0;
}

bool SelectMultipleTypes (HWND _hWnd, const CTypeSet * _pTypeSet, selected_types_t & _types) {
	CSelectType st_dlg (_pTypeSet, _types);
	return st_dlg.ShowModal (_hWnd) == 0;
}

